package ufrgs.inf.delphos.extraction.java;

import java.util.Arrays;

import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.*;
import org.eclipse.jdt.core.IMemberValuePair;
import org.eclipse.emf.ecore.util.BasicInternalEList;
import org.eclipse.emf.common.util.EList;

import ufrgs.inf.delphos.extraction.model.*;


public class JavaASTVisitor extends ASTVisitor {

	private DelphosSystemModelController delphosController;
	private DCompilationUnit currentCompilationUnit;
	
	
	public JavaASTVisitor(DelphosSystemModelController delphosController) {
		this.delphosController = delphosController;
	}
	
	
	/* VISITORS */
	
	@Override
	public boolean visit(ClassInstanceCreation node) {
		
		IJavaElement targetJavaElement = node.resolveTypeBinding().getJavaElement();
		
		DCompilationUnit compUnitTarget = delphosController.createCompilationUnit(targetJavaElement);
		delphosController.createRelation(DRelationType.CALL, currentCompilationUnit, compUnitTarget);
		
		return true;
	}
	
	@Override
	public boolean visit(FieldDeclaration node) {
		
		IJavaElement targetJavaElement = node.getType().resolveBinding().getJavaElement();
		
		DCompilationUnit compUnitTarget = delphosController.createCompilationUnit(targetJavaElement);
		delphosController.createRelation(DRelationType.CALL, currentCompilationUnit, compUnitTarget);
		
		return true;
	}
	
	@Override
	public boolean visit(ImportDeclaration node) {
		
		IJavaElement targetJavaElement = node.resolveBinding().getJavaElement();
		
		DCompilationUnit compUnitTarget = delphosController.createCompilationUnit(targetJavaElement);
		delphosController.createRelation(DRelationType.IMPORT, currentCompilationUnit, compUnitTarget);
		
		return true;
	}
	
	@Override
	public boolean visit(MethodInvocation node) {
		
		IJavaElement targetJavaElement = node.getName().resolveBinding().getJavaElement().getParent().getParent();
		
		DCompilationUnit compUnitTarget = delphosController.createCompilationUnit(targetJavaElement);
		delphosController.createRelation(DRelationType.CALL, currentCompilationUnit, compUnitTarget);

		return true;
	}
	
	@Override
	public boolean visit(MarkerAnnotation node) {
		
		IAnnotationBinding binding = node.resolveAnnotationBinding();
		String targetName;
		if(binding != null) {
			targetName = binding.getJavaElement().getElementName();
		} else {
			System.out.println("Error parsing a MarkerAnnotation.");
			return false;
		}
		
		return true;
	}
	
	@Override
	public boolean visit(NormalAnnotation node) {
		
		IAnnotationBinding binding = node.resolveAnnotationBinding();
		String targetName;
		if(binding != null) {
			targetName = binding.getJavaElement().getElementName();
		} else {
			System.out.println("Error parsing a NormalAnnotation.");
			return false;
		}
		
		return true;
	}
	
	@Override
	public boolean visit(SingleMemberAnnotation node) {
		
		IAnnotationBinding binding = node.resolveAnnotationBinding();
		String targetName;
		if(binding != null) {
			targetName = binding.getJavaElement().getElementName();
		} else {
			System.out.println("Error parsing a SingleMemberAnnotation.");
			return false;
		}		
		
		
		return true;
	}
	
	private EList<IMemberValuePair> getMappedAtributes(IAnnotation annotation) {
		
		IMemberValuePair[] map = null;
		
		try {
			map = annotation.getMemberValuePairs();
		} catch (JavaModelException e) {
			e.printStackTrace();
			return null;
		}

		BasicInternalEList<IMemberValuePair> atributes = new BasicInternalEList(IMemberValuePair.class, Arrays.asList(map));
		
		return atributes;
	}
	
	@Override
	public boolean visit(ThrowStatement node) {
				
		if(node.getExpression() instanceof ClassInstanceCreation) {
			
			IJavaElement targetJavaElement = ((ClassInstanceCreation) node.getExpression()).getType().resolveBinding().getJavaElement().getParent();
		
			DCompilationUnit compUnitTarget = delphosController.createCompilationUnit(targetJavaElement);
			delphosController.createRelation(DRelationType.EXCEPTION, currentCompilationUnit, compUnitTarget);
		}
		
		return true;
	}
	
	@Override
	public boolean visit(CatchClause node) {
		
		IJavaElement targetJavaElement = node.getException().getType().resolveBinding().getJavaElement().getParent();
		
		DCompilationUnit compUnitTarget = delphosController.createCompilationUnit(targetJavaElement);
		delphosController.createRelation(DRelationType.EXCEPTION, currentCompilationUnit, compUnitTarget);
		
		return true;
	}
	
	@Override
	public boolean visit(MethodDeclaration node) {
		
		for(Object o: node.thrownExceptions()) {
			
			IJavaElement targetJavaElement = ((Name)o).resolveBinding().getJavaElement().getParent();

			DCompilationUnit compUnitTarget = delphosController.createCompilationUnit(targetJavaElement);
			delphosController.createRelation(DRelationType.EXCEPTION, currentCompilationUnit, compUnitTarget);
		}
		
		return true;
	}
	
	@Override
	public boolean visit(SingleVariableDeclaration node) {
		
		IJavaElement targetJavaElement = node.getType().resolveBinding().getJavaElement();

		DCompilationUnit compUnitTarget = delphosController.createCompilationUnit(targetJavaElement);
		delphosController.createRelation(DRelationType.CALL, currentCompilationUnit, compUnitTarget);
		
		return true;
	}
	
	@Override
	public boolean visit(SuperConstructorInvocation node) {
		
		IJavaElement targetJavaElement = node.resolveConstructorBinding().getJavaElement();
		
		DCompilationUnit compUnitTarget = delphosController.createCompilationUnit(targetJavaElement);
		delphosController.createRelation(DRelationType.CALL, currentCompilationUnit, compUnitTarget);

		return true;
	}
	
	@Override
	public boolean visit(SuperMethodInvocation node) {
		
		IJavaElement targetJavaElement = node.resolveMethodBinding().getJavaElement();
		
		DCompilationUnit compUnitTarget = delphosController.createCompilationUnit(targetJavaElement);
		delphosController.createRelation(DRelationType.CALL, currentCompilationUnit, compUnitTarget);

		return true;
	}
	
	@Override
	public boolean visit(ParameterizedType node) {
		
		for (ITypeBinding parameterType : node.resolveBinding().getTypeArguments()) {
			
			IJavaElement targetJavaElement = parameterType.getJavaElement();
			
			DCompilationUnit compUnitTarget = delphosController.createCompilationUnit(targetJavaElement);
			delphosController.createRelation(DRelationType.INTERFACE, currentCompilationUnit, compUnitTarget);
		}

		return true;
	}
	
	@Override
	public boolean visit(TypeDeclaration node) {
		
		if ((node.resolveBinding().getSuperclass() != null) && (!node.resolveBinding().getSuperclass().getName().equals("Object"))) {

			IJavaElement targetJavaElement = node.resolveBinding().getSuperclass().getJavaElement().getParent();
			
			DCompilationUnit compUnitTarget = delphosController.createCompilationUnit(targetJavaElement);
			delphosController.createRelation(DRelationType.INHERITANCE, currentCompilationUnit, compUnitTarget);
		}
		
		if (node.resolveBinding().getInterfaces().length > 0) {

			ITypeBinding[] interfaces = node.resolveBinding().getInterfaces();
			for (ITypeBinding interfaceBinding : interfaces) {
			
				IJavaElement targetJavaElement = interfaceBinding.getJavaElement().getParent();

				DCompilationUnit compUnitTarget = delphosController.createCompilationUnit(targetJavaElement);
				delphosController.createRelation(DRelationType.INTERFACE, currentCompilationUnit, compUnitTarget);
			}
		}
		
		return true;
	}
	
	@Override
	public boolean visit(VariableDeclarationFragment node) {
		
		IJavaElement targetJavaElement = node.resolveBinding().getType().getJavaElement();
		
		DCompilationUnit compUnitTarget = delphosController.createCompilationUnit(targetJavaElement);
		delphosController.createRelation(DRelationType.CALL, currentCompilationUnit, compUnitTarget);

		return true;
	}
	
	public boolean visitNode(ASTNode node) {
		return true;
	}
	
	public void setCurrentCompilationUnit(DCompilationUnit currentCompilationUnit) {
		this.currentCompilationUnit = currentCompilationUnit;
	}
	
}
